#!/usr/bin/env python3
"""
S3 Object List Tool - ListObjectsV2 API 演示工具

这个工具演示如何使用 ListObjectsV2 API 列出 S3 存储桶中的对象。

使用示例:
    python s3_object_list.py --bucket my-bucket
    python s3_object_list.py --bucket my-bucket --prefix "folder/"
    python s3_object_list.py --bucket my-bucket --delimiter "/"
    python s3_object_list.py --bucket my-bucket --max-keys 100
    python s3_object_list.py --format json
    python s3_object_list.py --verbose
"""

import argparse
import boto3
import json
import sys
from datetime import datetime, timezone
from botocore.config import Config
from botocore.exceptions import ClientError, NoCredentialsError


class ObjectListClient:
    """S3 对象列表客户端"""
    
    def __init__(self, endpoint_url='http://localhost:8000', 
                 access_key='AKIAIOSFODNN7EXAMPLE', 
                 secret_key='wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
                 region='us-east-1'):
        """
        初始化对象列表客户端
        
        Args:
            endpoint_url: S3 服务端点
            access_key: AWS Access Key
            secret_key: AWS Secret Key
            region: AWS 区域
        """
        self.endpoint_url = endpoint_url
        self.access_key = access_key
        self.secret_key = secret_key
        self.region = region
        self._client = None
    
    def _get_client(self):
        """获取 S3 客户端"""
        if self._client is None:
            self._client = boto3.client(
                's3',
                endpoint_url=self.endpoint_url,
                aws_access_key_id=self.access_key,
                aws_secret_access_key=self.secret_key,
                region_name=self.region,
                config=Config(s3={'addressing_style': 'path'})
            )
        return self._client
    
    def list_objects(self, bucket_name, prefix=None, delimiter=None, max_keys=None, 
                           start_after=None, continuation_token=None, all_pages=False):
        """简单格式列出对象"""
        try:
            s3_client = self._get_client()
            
            # 准备参数
            list_args = {'Bucket': bucket_name}
            
            if prefix:
                list_args['Prefix'] = prefix
            if delimiter:
                list_args['Delimiter'] = delimiter
            if max_keys:
                list_args['MaxKeys'] = max_keys
            if start_after:
                list_args['StartAfter'] = start_after
            if continuation_token:
                list_args['ContinuationToken'] = continuation_token
            
            print(f"📋 列举对象 s3://{bucket_name}/")
            
            if prefix:
                print(f"   🔍 前缀过滤: {prefix}")
            if delimiter:
                print(f"   📁 分隔符: {delimiter}")
            if max_keys:
                print(f"   📊 最大返回数: {max_keys}")
            if start_after:
                print(f"   📍 开始位置: {start_after}")
            if continuation_token:
                print(f"   🔗 继续令牌: {continuation_token}")
            
            total_objects = 0
            total_size = 0
            page_num = 1
            
            while True:
                response = s3_client.list_objects_v2(**list_args)
                
                # 显示页面信息
                if all_pages and page_num > 1:
                    print(f"\n--- 第 {page_num} 页 ---")
                
                print(f"KeyCount: {response.get('KeyCount', 0)}")
                print(f"IsTruncated: {response.get('IsTruncated', False)}")
                
                if response.get('ContinuationToken'):
                    print(f"ContinuationToken: {response.get('ContinuationToken')}")
                
                if response.get('NextContinuationToken'):
                    print(f"NextContinuationToken: {response.get('NextContinuationToken')}")
                
                # 显示对象列表
                contents = response.get('Contents', [])
                if contents:
                    print(f"\n对象列表 ({len(contents)} 个):")
                    for obj in contents:
                        size = obj.get('Size', 0)
                        total_objects += 1
                        total_size += size
                        print(f"  {obj.get('Key')} ({size} 字节, {obj.get('LastModified')}, ETag: {obj.get('ETag')})")
                
                # 显示公共前缀（模拟目录）
                common_prefixes = response.get('CommonPrefixes', [])
                if common_prefixes:
                    print(f"\n公共前缀 ({len(common_prefixes)} 个):")
                    for prefix in common_prefixes:
                        print(f"  {prefix.get('Prefix')} (目录)")
                
                # 检查是否还有更多页面
                if not all_pages or not response.get('IsTruncated'):
                    break
                
                # 准备下一页
                next_token = response.get('NextContinuationToken')
                if not next_token:
                    break
                
                list_args['ContinuationToken'] = next_token
                page_num += 1
            
            # 显示总计信息
            if all_pages and total_objects > 0:
                print(f"\n📊 总计:")
                print(f"  对象数量: {total_objects}")
                print(f"  总大小: {total_size} 字节 ({total_size / 1024 / 1024:.2f} MB)")
            
            return True
            
        except ClientError as e:
            error_code = e.response['Error']['Code']
            error_msg = e.response['Error']['Message']
            
            if error_code == 'NoSuchBucket':
                print(f"❌ 桶不存在: {bucket_name}")
            else:
                print(f"❌ 列举失败 [{error_code}]: {error_msg}")
            
            return False
        except NoCredentialsError:
            print("❌ 错误: 未找到 AWS 凭据")
            return False
        except Exception as e:
            print(f"❌ 未知错误: {e}")
            return False
    
    def list_objects_verbose(self, bucket_name, prefix=None, delimiter=None, max_keys=None, 
                            start_after=None, continuation_token=None, all_pages=False):
        """详细格式列出对象"""
        try:
            s3_client = self._get_client()
            
            print("📋 对象列举详细信息:")
            print("=" * 50)
            print(f"   🗂️  桶名称: {bucket_name}")
            print(f"   🌐 区域: {self.region}")
            print(f"   🔗 服务端点: {self.endpoint_url}")
            print(f"   📅 列举时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
            
            if prefix:
                print(f"   🔍 前缀过滤: {prefix}")
            if delimiter:
                print(f"   📁 分隔符: {delimiter}")
            if max_keys:
                print(f"   📊 最大返回数: {max_keys}")
            if start_after:
                print(f"   📍 开始位置: {start_after}")
            if continuation_token:
                print(f"   🔗 继续令牌: {continuation_token}")
            
            print()
            
            # 准备参数
            list_args = {'Bucket': bucket_name}
            
            if prefix:
                list_args['Prefix'] = prefix
            if delimiter:
                list_args['Delimiter'] = delimiter
            if max_keys:
                list_args['MaxKeys'] = max_keys
            if start_after:
                list_args['StartAfter'] = start_after
            if continuation_token:
                list_args['ContinuationToken'] = continuation_token
            
            total_objects = 0
            total_size = 0
            page_num = 1
            
            while True:
                print(f"   🔧 正在列举第 {page_num} 页...")
                response = s3_client.list_objects_v2(**list_args)
                
                # 显示页面信息
                if all_pages and page_num > 1:
                    print(f"\n   --- 第 {page_num} 页 ---")
                
                print(f"   KeyCount: {response.get('KeyCount', 0)}")
                print(f"   IsTruncated: {response.get('IsTruncated', False)}")
                
                if response.get('ContinuationToken'):
                    print(f"   ContinuationToken: {response.get('ContinuationToken')}")
                
                if response.get('NextContinuationToken'):
                    print(f"   NextContinuationToken: {response.get('NextContinuationToken')}")
                
                # 显示对象列表
                contents = response.get('Contents', [])
                if contents:
                    print(f"\n   对象列表 ({len(contents)} 个):")
                    for obj in contents:
                        size = obj.get('Size', 0)
                        total_objects += 1
                        total_size += size
                        print(f"     {obj.get('Key')} ({size} 字节, {obj.get('LastModified')}, ETag: {obj.get('ETag')})")
                
                # 显示公共前缀（模拟目录）
                common_prefixes = response.get('CommonPrefixes', [])
                if common_prefixes:
                    print(f"\n   公共前缀 ({len(common_prefixes)} 个):")
                    for prefix in common_prefixes:
                        print(f"     {prefix.get('Prefix')} (目录)")
                
                # 检查是否还有更多页面
                if not all_pages or not response.get('IsTruncated'):
                    break
                
                # 准备下一页
                next_token = response.get('NextContinuationToken')
                if not next_token:
                    break
                
                list_args['ContinuationToken'] = next_token
                page_num += 1
            
            # 显示总计信息
            if all_pages and total_objects > 0:
                print(f"\n   📊 总计:")
                print(f"     对象数量: {total_objects}")
                print(f"     总大小: {total_size} 字节 ({total_size / 1024 / 1024:.2f} MB)")
            
            print(f"   📊 列举完成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
            
            return True
            
        except ClientError as e:
            error_code = e.response['Error']['Code']
            error_msg = e.response['Error']['Message']
            
            print(f"   ❌ 列举失败:")
            print(f"      错误代码: {error_code}")
            print(f"      错误信息: {error_msg}")
            
            if error_code == 'NoSuchBucket':
                print(f"   📋 状态: 桶不存在")
            else:
                print(f"   📋 状态: 列举失败")
            
            return False
        except NoCredentialsError:
            print("   ❌ 错误: 未找到 AWS 凭据")
            return False
        except Exception as e:
            print(f"   ❌ 未知错误: {e}")
            return False
    
    def list_objects_json(self, bucket_name, prefix=None, delimiter=None, max_keys=None, 
                         start_after=None, continuation_token=None, all_pages=False):
        """JSON 格式列出对象"""
        try:
            s3_client = self._get_client()
            
            # 准备参数
            list_args = {'Bucket': bucket_name}
            
            if prefix:
                list_args['Prefix'] = prefix
            if delimiter:
                list_args['Delimiter'] = delimiter
            if max_keys:
                list_args['MaxKeys'] = max_keys
            if start_after:
                list_args['StartAfter'] = start_after
            if continuation_token:
                list_args['ContinuationToken'] = continuation_token
            
            all_objects = []
            all_prefixes = []
            total_objects = 0
            total_size = 0
            page_num = 1
            
            while True:
                response = s3_client.list_objects_v2(**list_args)
                
                # 收集对象信息
                contents = response.get('Contents', [])
                for obj in contents:
                    size = obj.get('Size', 0)
                    total_objects += 1
                    total_size += size
                    
                    obj_info = {
                        "key": obj.get('Key'),
                        "size": size,
                        "last_modified": obj.get('LastModified').isoformat() if obj.get('LastModified') else None,
                        "etag": obj.get('ETag'),
                        "storage_class": obj.get('StorageClass')
                    }
                    all_objects.append(obj_info)
                
                # 收集前缀信息
                common_prefixes = response.get('CommonPrefixes', [])
                for prefix in common_prefixes:
                    all_prefixes.append(prefix.get('Prefix'))
                
                # 检查是否还有更多页面
                if not all_pages or not response.get('IsTruncated'):
                    break
                
                # 准备下一页
                next_token = response.get('NextContinuationToken')
                if not next_token:
                    break
                
                list_args['ContinuationToken'] = next_token
                page_num += 1
            
            result = {
                "success": True,
                "bucket_name": bucket_name,
                "prefix": prefix,
                "delimiter": delimiter,
                "max_keys": max_keys,
                "start_after": start_after,
                "continuation_token": continuation_token,
                "all_pages": all_pages,
                "total_objects": total_objects,
                "total_size_bytes": total_size,
                "total_size_mb": round(total_size / 1024 / 1024, 2),
                "objects": all_objects,
                "common_prefixes": all_prefixes,
                "list_time": datetime.now().isoformat()
            }
            
            print(json.dumps(result, indent=2, ensure_ascii=False))
            return True
            
        except ClientError as e:
            error_code = e.response['Error']['Code']
            error_msg = e.response['Error']['Message']
            
            result = {
                "success": False,
                "bucket_name": bucket_name,
                "error": error_code,
                "error_message": error_msg,
                "list_time": datetime.now().isoformat()
            }
            
            print(json.dumps(result, indent=2, ensure_ascii=False))
            return False
        except NoCredentialsError:
            result = {
                "success": False,
                "error": "NoCredentials",
                "error_message": "未找到 AWS 凭据",
                "list_time": datetime.now().isoformat()
            }
            print(json.dumps(result, indent=2, ensure_ascii=False))
            return False
        except Exception as e:
            result = {
                "success": False,
                "error": "Unknown",
                "error_message": str(e),
                "list_time": datetime.now().isoformat()
            }
            print(json.dumps(result, indent=2, ensure_ascii=False))
            return False


def parse_args():
    """解析命令行参数"""
    parser = argparse.ArgumentParser(
        description='S3 ListObjectsV2 API 演示工具',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
示例:
  %(prog)s --bucket my-bucket                           # 列出所有对象
  %(prog)s --bucket my-bucket --prefix "folder/"        # 前缀过滤
  %(prog)s --bucket my-bucket --delimiter "/"           # 目录分隔符
  %(prog)s --bucket my-bucket --max-keys 100            # 限制返回数量
  %(prog)s --bucket my-bucket --all-pages               # 获取所有页面
  %(prog)s --format json                                # JSON 格式输出
  %(prog)s --verbose                                    # 详细格式输出
  %(prog)s --access-key KEY --secret-key SECRET         # 指定凭据
        """
    )
    
    parser.add_argument(
        '--host',
        default='http://localhost:8000',
        help='S3 服务端点 (默认: http://localhost:8000)'
    )
    
    parser.add_argument(
        '--access-key',
        default='AKIAIOSFODNN7EXAMPLE',
        help='AWS Access Key (默认: 测试密钥)'
    )
    
    parser.add_argument(
        '--secret-key',
        default='wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
        help='AWS Secret Key (默认: 测试密钥)'
    )
    
    parser.add_argument(
        '--region',
        default='us-east-1',
        help='AWS 区域 (默认: us-east-1)'
    )
    
    parser.add_argument(
        '--format',
        choices=['simple', 'verbose', 'json'],
        default='simple',
        help='输出格式 (默认: simple)'
    )
    
    parser.add_argument(
        '--verbose',
        action='store_true',
        help='详细输出模式 (等同于 --format verbose)'
    )
    
    parser.add_argument(
        '--bucket',
        required=True,
        help='桶名称'
    )
    
    parser.add_argument(
        '--prefix',
        help='对象键名前缀'
    )
    
    parser.add_argument(
        '--delimiter',
        help='分隔符（通常用 / 来模拟目录结构）'
    )
    
    parser.add_argument(
        '--max-keys',
        type=int,
        help='返回的最大对象数'
    )
    
    parser.add_argument(
        '--start-after',
        help='开始列举的起始键名'
    )
    
    parser.add_argument(
        '--continuation-token',
        help='分页继续令牌'
    )
    
    parser.add_argument(
        '--all-pages',
        action='store_true',
        help='获取所有页面（忽略 max-keys 限制）'
    )
    
    return parser.parse_args()


def main():
    """主函数"""
    args = parse_args()
    
    # 如果指定了 --verbose，覆盖 format 设置
    if args.verbose:
        args.format = 'verbose'
    
    # 创建客户端
    client = ObjectListClient(
        endpoint_url=args.host,
        access_key=args.access_key,
        secret_key=args.secret_key,
        region=args.region
    )
    
    # 根据格式执行相应操作
    success = False
    if args.format == 'simple':
        success = client.list_objects_simple(
            args.bucket, args.prefix, args.delimiter, args.max_keys,
            args.start_after, args.continuation_token, args.all_pages
        )
    elif args.format == 'verbose':
        success = client.list_objects_verbose(
            args.bucket, args.prefix, args.delimiter, args.max_keys,
            args.start_after, args.continuation_token, args.all_pages
        )
    elif args.format == 'json':
        success = client.list_objects_json(
            args.bucket, args.prefix, args.delimiter, args.max_keys,
            args.start_after, args.continuation_token, args.all_pages
        )
    
    # 设置退出码
    sys.exit(0 if success else 1)


if __name__ == '__main__':
    main()
